/* Atom.cs -- 
   Copyright (C) 2007, 2008  Casey Marshall.
   Copyright (C) 2008 Pacific Biosciences, Inc.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */


using System;
using System.Globalization;
using System.IO;
using System.Text;

namespace SexpSharp
{
    /// <summary>
    /// An atom expression. Atoms are simple sequences of bytes.
    /// </summary>
    public class Atom : Expression, ICloneable, IConvertible
    {
		private byte[] bytes;

		public Atom()
		{
			bytes = new byte[0];
		}

		public Atom(byte[] buffer)
		{
			bytes = (byte[]) buffer.Clone();
		}

		public Atom(byte[] buffer, int offset, int length)
		{
			bytes = new byte[length];
			Buffer.BlockCopy(buffer, offset, bytes, 0, length);
		}

		public Atom(string str)
		{
			bytes = new UTF8Encoding().GetBytes(str);
		}

		public Atom(bool b)
		{
			bytes = new UTF8Encoding().GetBytes(b.ToString());
		}

		public Atom(byte b)
		{
			bytes = new byte[] { b };
		}

		public Atom(sbyte b) : this((byte) b) { }

		public Atom(ushort s)
		{
            if (s < 0x100U)
                bytes = new byte[] { (byte) s };
            else
                bytes = new byte[] { (byte) (s >> 8), (byte) s };
		}

		public Atom(short s) : this((ushort) s) { }

		public Atom(uint i)
		{
            if (i < 0x100U)
                bytes = new byte[] { (byte) i };
            else if (i < 0x10000U)
                bytes = new byte[] { (byte) (i >> 8), (byte) i };
            else if (i < 0x1000000U)
                bytes = new byte[] { (byte) (i >> 16), (byte) (i >> 8),
                                     (byte) i };
            else
                bytes = new byte[] { (byte) (i >> 24), (byte) (i >> 16),
                                     (byte) (i >>  8), (byte) i };
		}

		public Atom(int i) : this((uint) i) { }

		public Atom(ulong l)
		{
            if (l < 0x100UL)
                bytes = new byte[] { (byte) l };
            else if (l < 0x10000UL)
                bytes = new byte[] { (byte) (l >> 8), (byte) l };
            else if (l < 0x1000000UL)
                bytes = new byte[] { (byte) (l >> 16), (byte) (l >> 8),
                                     (byte) l };
            else if (l < 0x100000000UL)
                bytes = new byte[] { (byte) (l >> 24), (byte) (l >> 16),
                                     (byte) (l >>  8), (byte)  l };
            else if (l < 0x10000000000UL)
                bytes = new byte[] { (byte) (l >> 32), (byte) (l >> 24),
                                     (byte) (l >> 16), (byte) (l >>  8),
                                     (byte)  l };
            else if (l < 0x1000000000000UL)
                bytes = new byte[] { (byte) (l >> 40), (byte) (l >> 32),
                                     (byte) (l >> 24), (byte) (l >> 16),
                                     (byte) (l >>  8), (byte)  l };
            else if (l < 0x100000000000000UL)
                bytes = new byte[] { (byte) (l >> 48), (byte) (l >> 40),
                                     (byte) (l >> 32), (byte) (l >> 24),
                                     (byte) (l >> 16), (byte) (l >>  8),
                                     (byte)  l };
            else
                bytes = new byte[] { (byte) (l >> 56), (byte) (l >> 48),
                                     (byte) (l >> 40), (byte) (l >> 32),
                                     (byte) (l >> 24), (byte) (l >> 16),
                                     (byte) (l >>  8), (byte) l };
		}

		public Atom(long l) : this((ulong) l) { }

		public Atom(uint i, bool compact)
		{
			if (compact && i < 0x01000000)
			{
				if (i < 0x100)
					bytes = new byte[] { (byte) i };
				else if (i < 0x10000)
					bytes = new byte[] { (byte) (i >> 8), (byte) i };
				else
					bytes = new byte[] { (byte) (i >> 16), (byte) (i >> 8),
										 (byte) i };
			}
			else
			{
				bytes = new byte[] { (byte) (i >> 24), (byte) (i >> 16),
									 (byte) (i >>  8), (byte) i };
			}
		}

		public Atom(decimal d) : this(d.ToString("e"))
		{
		}

		public Atom(double d) : this(d.ToString("e"))
		{
		}

		public Atom(float f) : this(f.ToString("e"))
		{
		}

		public Atom(DateTime date) : this(date.ToString())
		{
		}

		/// <summary>
		/// The atom bytes. When getting or setting this property, the
		/// value is cloned.
		/// </summary>
		public byte[] Bytes
		{
			get
			{
				return (byte[]) bytes.Clone();
			}

			set
			{
				bytes = (byte[]) value.Clone();
			}
		}

		/// <summary>
		/// Tell if this atom is a token.
		/// </summary>
		public bool IsToken
		{
			get {
				if (bytes.Length == 0)
					return false;
				if (!Parser.IsTokenStart(bytes[0] & 0xFF))
					return false;
				for (int i = 1; i < bytes.Length; i++)
				{
					if (!Parser.IsTokenPart(bytes[i] & 0xFF))
						return false;
				}
				return true;
			}
		}

		/// <summary>
		/// Tell if this is a quotable atom.
		/// </summary>
		public bool IsQuotable
		{
			get {
				for (int i = 0; i < bytes.Length; i++)
				{
					if (!Parser.IsQuotableChar(bytes[i] & 0xFF))
						return false;
				}
				return true;
			}
		}

		public override void WriteVerbatim(Stream o)
		{
			StringBuilder str = new StringBuilder();
			str.Append(bytes.Length);
			str.Append(':');
			byte[] len = Encoding.ASCII.GetBytes(str.ToString());
			o.Write(len, 0, len.Length);
			o.Write(bytes, 0, bytes.Length);
		}

		public override object Clone()
		{
			return this.MemberwiseClone();
		}

		public override string ToAdvancedString()
		{
			StringBuilder str = new StringBuilder();
			if (this.IsToken)
				str.Append(Encoding.ASCII.GetString(bytes));
			else if (this.IsQuotable)
			{
				str.Append('"');
				str.Append(Encoding.ASCII.GetString(bytes));
				str.Append('"');
			}
			else if (bytes.Length <= 16)
			{
				str.Append('#');
				for (int i = 0; i < bytes.Length; i++)
				{
					int x = (bytes[i] & 0xFF) >> 4;
					if (x < 10)
						str.Append((char) (x + '0'));
					else
						str.Append((char) ((x - 10) + 'a'));
					x = (bytes[i] & 0xF);
					if (x < 10)
						str.Append((char) (x + '0'));
					else
						str.Append((char) ((x - 10) + 'a'));
				}
				str.Append('#');
			}
			else
			{
				str.Append('|');
				str.Append(Convert.ToBase64String(bytes));
				str.Append('|');
			}
			return str.ToString();
		}

		public override bool Equals(object o)
		{
			if (!(o is Atom))
				return false;
			Atom that = o as Atom;
			if (that.bytes.Length != bytes.Length)
			{
				return false;
			}
			for (int i = 0; i < bytes.Length; i++)
			{
				if (that.bytes[i] != bytes[i])
					return false;
			}
			return true;
		}

		public override int GetHashCode()
		{
			int code = 0;
			for (int i = 0; i < bytes.Length; i++)
			{
				code = code ^ ((bytes[i] & 0xFF) << (i & 3));
			}
			return code;
		}

		public override string ToString()
		{
			return ToString(null);
		}

		public uint ToInteger()
		{
			if (bytes.Length == 1)
				return bytes[0] & 0xFFU;
			if (bytes.Length == 2)
				return (((bytes[0] & 0xFFU) << 8)
						| (bytes[1] & 0xFFU));
			if (bytes.Length == 3)
				return (((bytes[0] & 0xFFU) << 16)
						| ((bytes[1] & 0xFFU) << 8)
						| (bytes[2] & 0xFFU));
			if (bytes.Length == 4)
				return (((bytes[0] & 0xFFU) << 24)
						| ((bytes[1] & 0xFFU) << 16)
						| ((bytes[2] & 0xFFU) << 8)
						| (bytes[3] & 0xFFU));
			throw new ArgumentException("atom is to large for an integer");
		}

		public TypeCode GetTypeCode()
		{
			return TypeCode.Object;
		}

		public string ToString(IFormatProvider p)
		{
			UTF8Encoding enc = new UTF8Encoding();
			return enc.GetString(bytes, 0, bytes.Length);
		}

		public bool ToBoolean(IFormatProvider p)
		{
			return Boolean.Parse(ToString(p));
		}

		public byte ToByte(IFormatProvider p)
		{
			if (bytes.Length != 1)
				throw new InvalidCastException("atom is not a single byte");
			return bytes[0];
		}

		public char ToChar(IFormatProvider p)
		{
			if (bytes.Length > 2)
				throw new InvalidCastException("atom is not two bytes");
            if (bytes.Length == 1)
                return (char) (bytes[0] & 0xFF);
			return (char) (((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF));
		}

		public DateTime ToDateTime(IFormatProvider p)
		{
			return DateTime.Parse(ToString(p), p);
		}

		public decimal ToDecimal(IFormatProvider p)
		{
			return Decimal.Parse(ToString(p), (NumberStyles.AllowDecimalPoint
											   | NumberStyles.AllowExponent
											   | NumberStyles.AllowLeadingSign));
		}

		public double ToDouble(IFormatProvider p)
		{
			return Double.Parse(ToString(p), (NumberStyles.AllowDecimalPoint
											  | NumberStyles.AllowExponent
											  | NumberStyles.AllowLeadingSign));
		}

		public short ToInt16(IFormatProvider p)
		{
			if (bytes.Length > 2)
				throw new InvalidCastException("atom is not two bytes");
            if (bytes.Length == 1)
                return (short) (bytes[0] & 0xFF);
			return (short) (((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF));
		}

		public int ToInt32(IFormatProvider p)
		{
			if (bytes.Length > 4)
				throw new InvalidCastException("atom is not four bytes");
            int ret = 0;
            int i = 0;
            if (bytes.Length == 4) ret = ((bytes[i++] & 0xFF) << 24);
            if (bytes.Length >= 3) ret = ret | ((bytes[i++] & 0xFF) << 16); 
            if (bytes.Length >= 2) ret = ret | ((bytes[i++] & 0xFF) <<  8); 
            if (bytes.Length >= 1) ret = ret |  (bytes[i++] & 0xFF);
            return ret;
		}

		public long ToInt64(IFormatProvider p)
		{
			if (bytes.Length > 8)
				throw new InvalidCastException("atom is not eight bytes");
            long ret = 0;
            int i = 0;
            if (bytes.Length == 8) ret = ((bytes[i++] & 0xFFL) << 56);
            if (bytes.Length >= 7) ret = ret | ((bytes[i++] & 0xFFL) << 48); 
            if (bytes.Length >= 6) ret = ret | ((bytes[i++] & 0xFFL) << 40); 
            if (bytes.Length >= 5) ret = ret | ((bytes[i++] & 0xFFL) << 32);
            if (bytes.Length >= 4) ret = ret | ((bytes[i++] & 0xFFL) << 24);
            if (bytes.Length >= 3) ret = ret | ((bytes[i++] & 0xFFL) << 16); 
            if (bytes.Length >= 2) ret = ret | ((bytes[i++] & 0xFFL) <<  8); 
            if (bytes.Length >= 1) ret = ret |  (bytes[i++] & 0xFFL);
            return ret;
		}

		public sbyte ToSByte(IFormatProvider p)
		{
			return (sbyte) ToByte(p);
		}

		public float ToSingle(IFormatProvider p)
		{
			return Single.Parse(ToString(p), (NumberStyles.AllowDecimalPoint
											  | NumberStyles.AllowExponent
											  | NumberStyles.AllowLeadingSign));
		}

		public object ToType(Type t, IFormatProvider p)
		{
			throw new InvalidCastException("don't know how to convert atoms to " + t);
		}

		public ushort ToUInt16(IFormatProvider p)
		{
			if (bytes.Length > 2)
				throw new InvalidCastException("atom is not two bytes");
            if (bytes.Length == 1)
                return (ushort) (bytes[0] & 0xFFU);
			return (ushort) (((bytes[0] & 0xFFU) << 8) | (bytes[1] & 0xFFU));
		}

		public uint ToUInt32(IFormatProvider p)
		{
			if (bytes.Length > 4)
				throw new InvalidCastException("atom is not four bytes");
            uint ret = 0;
            int i = 0;
            if (bytes.Length == 4) ret = ((bytes[i++] & 0xFFU) << 24);
            if (bytes.Length >= 3) ret = ret | ((bytes[i++] & 0xFFU) << 16); 
            if (bytes.Length >= 2) ret = ret | ((bytes[i++] & 0xFFU) <<  8); 
            if (bytes.Length >= 1) ret = ret |  (bytes[i++] & 0xFFU);
            return ret;
		}

		public ulong ToUInt64(IFormatProvider p)
		{
			if (bytes.Length != 8)
				throw new InvalidCastException("atom is not eight bytes");
            ulong ret = 0;
            int i = 0;
            if (bytes.Length == 8) ret = ((bytes[i++] & 0xFFUL) << 56);
            if (bytes.Length >= 7) ret = ret | ((bytes[i++] & 0xFFUL) << 48); 
            if (bytes.Length >= 6) ret = ret | ((bytes[i++] & 0xFFUL) << 40); 
            if (bytes.Length >= 5) ret = ret | ((bytes[i++] & 0xFFUL) << 32);
            if (bytes.Length >= 4) ret = ret | ((bytes[i++] & 0xFFUL) << 24);
            if (bytes.Length >= 3) ret = ret | ((bytes[i++] & 0xFFUL) << 16); 
            if (bytes.Length >= 2) ret = ret | ((bytes[i++] & 0xFFUL) <<  8); 
            if (bytes.Length >= 1) ret = ret |  (bytes[i++] & 0xFFUL);
            return ret;
		}
    }
}